import asyncio
import tkinter as tk
from tkinter import ttk, colorchooser
import json
from pathlib import Path
import platform
from PIL import Image, ImageDraw, ImageTk, ImageFont
import time
import csv
from datetime import datetime

from bleak import BleakScanner, BleakClient

# 心率服务和特征的 UUID
HEART_RATE_SERVICE_UUID = "0000180d-0000-1000-8000-00805f9b34fb"
HEART_RATE_MEASUREMENT_UUID = "00002a37-0000-1000-8000-00805f9b34fb"

# 创建文件夹
CONFIG_DIR = Path("config")
DATA_DIR = Path("data")
CONFIG_DIR.mkdir(exist_ok=True)
DATA_DIR.mkdir(exist_ok=True)

CONFIG_FILE = CONFIG_DIR / "config.json"
DATA_FILE = DATA_DIR / "heart_rate_data.csv"


class HeartRateApp:
    def __init__(self, root):
        self.root = root
        self.root.title("简易手环心率监测展示")
        self.root.geometry("400x450")  # 增加高度以容纳新设置

        self.devices = {}
        self.client = None
        self.connected_device = None
        self.monitor_window = None
        self.monitor_hr_label = None
        self.is_scanning = True
        self.scan_task = None
        self.heartbeat_task = None
        self.last_heart_rate_time = 0
        self.connection_timeout = 10  # 10秒无数据则自动返回

        self._offset_x = 0
        self._offset_y = 0

        # 初始化CSV文件
        self.init_csv_file()

        self.config = self.load_config()
        self.setup_ui()
        self.start_continuous_scan()

    def init_csv_file(self):
        """初始化CSV文件，如果不存在则创建并写入表头"""
        if not DATA_FILE.exists():
            try:
                with open(DATA_FILE, 'w', newline='', encoding='utf-8') as f:
                    writer = csv.writer(f)
                    writer.writerow(['时间日期', '心跳数据(BPM)'])
                print(f"创建新的数据文件: {DATA_FILE}")
            except Exception as e:
                print(f"创建数据文件失败: {e}")

    def load_config(self):
        default_config = {
            "font_color": "#000000",
            "font_size": 32,
            "monitor_pos": None,
            "custom_text": "BPM"  # 默认文字
        }
        if not CONFIG_FILE.exists():
            return default_config
        try:
            with open(CONFIG_FILE, "r", encoding='utf-8') as f:
                config = json.load(f)
                for key, value in default_config.items():
                    config.setdefault(key, value)
                return config
        except (json.JSONDecodeError, IOError):
            return default_config

    def save_config(self):
        if self.monitor_window:
            self.config['monitor_pos'] = self.monitor_window.geometry()
        try:
            self.config['font_size'] = int(self.font_size_var.get())
            self.config['custom_text'] = self.custom_text_var.get()
            with open(CONFIG_FILE, "w", encoding='utf-8') as f:
                json.dump(self.config, f, indent=4, ensure_ascii=False)
        except (IOError, ValueError) as e:
            self.update_status(f"无法保存配置: {e}")

    def save_heart_rate_data(self, heart_rate):
        """保存心率数据到CSV文件"""
        try:
            # 获取当前时间
            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

            # 追加数据到CSV文件
            with open(DATA_FILE, 'a', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                writer.writerow([current_time, heart_rate])

            print(f"保存心率数据: {current_time}, {heart_rate} BPM")
        except Exception as e:
            print(f"保存心率数据失败: {e}")
            self.update_status(f"数据保存失败: {e}")

    def get_font_path(self):
        """获取系统中文字体路径"""
        system = platform.system()

        if system == "Windows":
            # Windows常见中文字体路径
            font_paths = [
                "C:/Windows/Fonts/msyh.ttc",  # 微软雅黑
                "C:/Windows/Fonts/simhei.ttf",  # 黑体
                "C:/Windows/Fonts/simsun.ttc",  # 宋体
                "C:/Windows/Fonts/arial.ttf",  # Arial
            ]
        elif system == "Darwin":  # macOS
            font_paths = [
                "/System/Library/Fonts/PingFang.ttc",  # 苹方
                "/System/Library/Fonts/STHeiti Light.ttc",  # 华文黑体
                "/System/Library/Fonts/Arial.ttf",
            ]
        else:  # Linux
            font_paths = [
                "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
                "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
                "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
            ]

        # 尝试找到可用的字体
        for font_path in font_paths:
            if Path(font_path).exists():
                return font_path

        # 如果都找不到，返回None，使用默认字体
        return None

    def setup_ui(self):
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        scan_frame = ttk.LabelFrame(main_frame, text="设备列表")
        scan_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        self.device_listbox = tk.Listbox(scan_frame, height=10)
        self.device_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        scrollbar = ttk.Scrollbar(scan_frame, orient=tk.VERTICAL, command=self.device_listbox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.device_listbox.config(yscrollcommand=scrollbar.set)

        self.connect_button = ttk.Button(main_frame, text="连接", command=self.start_connect, state=tk.DISABLED)
        self.connect_button.pack(fill=tk.X, pady=5)

        self.device_listbox.bind('<<ListboxSelect>>', self.on_device_select)

        settings_frame = ttk.LabelFrame(main_frame, text="设置")
        settings_frame.pack(fill=tk.X, pady=10)

        # 第一行：颜色和字体大小
        first_row = ttk.Frame(settings_frame)
        first_row.pack(fill=tk.X, pady=2)

        color_button = ttk.Button(first_row, text="设置字体颜色", command=self.choose_color)
        color_button.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))

        ttk.Label(first_row, text="字体大小:").pack(side=tk.RIGHT, padx=(0, 5))
        self.font_size_var = tk.StringVar(value=str(self.config.get('font_size', 32)))
        font_size_spinbox = ttk.Spinbox(first_row, from_=10, to=100, textvariable=self.font_size_var, width=5)
        font_size_spinbox.pack(side=tk.RIGHT)

        # 第二行：自定义文字
        second_row = ttk.Frame(settings_frame)
        second_row.pack(fill=tk.X, pady=2)

        ttk.Label(second_row, text="自定义文字:").pack(side=tk.LEFT, padx=(0, 5))
        self.custom_text_var = tk.StringVar(value=self.config.get('custom_text', 'BPM'))
        custom_text_entry = ttk.Entry(second_row, textvariable=self.custom_text_var)
        custom_text_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))

        # 添加中英文预设按钮
        preset_frame = ttk.Frame(second_row)
        preset_frame.pack(side=tk.RIGHT)

        ttk.Button(preset_frame, text="BPM", width=4,
                   command=lambda: self.set_custom_text("BPM")).pack(side=tk.LEFT, padx=1)
        ttk.Button(preset_frame, text="bpm", width=4,
                   command=lambda: self.set_custom_text("bpm")).pack(side=tk.LEFT, padx=1)
        ttk.Button(preset_frame, text="次/分", width=4,
                   command=lambda: self.set_custom_text("次/分")).pack(side=tk.LEFT, padx=1)
        ttk.Button(preset_frame, text="心跳", width=4,
                   command=lambda: self.set_custom_text("心跳")).pack(side=tk.LEFT, padx=1)

        # 第三行：数据文件信息
        third_row = ttk.Frame(settings_frame)
        third_row.pack(fill=tk.X, pady=2)

        data_info_label = ttk.Label(third_row, text=f"数据文件: {DATA_FILE}",
                                    font=('TkDefaultFont', 8), foreground='gray')
        data_info_label.pack(side=tk.LEFT)

        # 第四行：提示信息
        fourth_row = ttk.Frame(settings_frame)
        fourth_row.pack(fill=tk.X, pady=2)

        hint_label = ttk.Label(fourth_row, text="提示：支持中英文自定义文字，心率数据自动保存",
                               font=('TkDefaultFont', 8))
        hint_label.pack(side=tk.LEFT)

        self.status_label = ttk.Label(self.root, text="准备就绪", relief=tk.SUNKEN, anchor=tk.W)
        self.status_label.pack(side=tk.BOTTOM, fill=tk.X)

    def set_custom_text(self, text):
        """设置自定义文字"""
        self.custom_text_var.set(text)
        # 立即更新配置并保存
        self.config['custom_text'] = text
        self.save_config()
        self.update_status(f"自定义文字已更新为: {text}")

    def choose_color(self):
        color_code = colorchooser.askcolor(title="选择字体颜色", initialcolor=self.config['font_color'])
        if color_code and color_code[1]:
            self.config['font_color'] = color_code[1]
            self.save_config()  # 立即保存颜色配置
            self.update_status(f"字体颜色已设置为: {self.config['font_color']}")

    def on_device_select(self, event):
        """设备选择事件处理"""
        selection = self.device_listbox.curselection()
        if selection:
            self.connect_button.config(state=tk.NORMAL)
            self.update_status("已选择设备，点击连接按钮开始连接")
        else:
            self.connect_button.config(state=tk.DISABLED)

    def start_continuous_scan(self):
        self.is_scanning = True
        self.scan_task = asyncio.create_task(self.continuous_scan())

    async def continuous_scan(self):
        while self.is_scanning:
            self.update_status("正在扫描...")
            try:
                found_devices = await BleakScanner.discover(timeout=3.0)
                for device in found_devices:
                    if device.address not in self.devices:
                        self.devices[device.address] = device
                        device_name = device.name or "未知设备"
                        self.device_listbox.insert(tk.END, f"{device_name} ({device.address})")

                self.update_status(f"扫描中... 当前发现 {len(self.devices)} 个设备。")
            except Exception as e:
                self.update_status(f"扫描出错: {e}")
            await asyncio.sleep(5)

    def start_connect(self):
        """开始连接设备"""
        selection = self.device_listbox.curselection()
        if not selection:
            self.update_status("请先选择一个设备")
            return

        selected_text = self.device_listbox.get(selection[0])
        address = selected_text.split('(')[-1].strip(')')
        self.connected_device = self.devices.get(address)

        if self.connected_device:
            self.is_scanning = False
            if self.scan_task:
                self.scan_task.cancel()
            self.connect_button.config(state=tk.DISABLED)
            self.update_status(f"正在连接到 {self.connected_device.name}...")
            asyncio.create_task(self.connect_device())
        else:
            self.update_status("设备未找到，请重新扫描")

    async def connect_device(self):
        """连接设备"""
        if not self.connected_device:
            self.update_status("没有选择设备")
            return

        try:
            self.client = BleakClient(self.connected_device.address)
            await self.client.connect()
            self.update_status(f"已连接。正在监听心率...")
            await self.client.start_notify(HEART_RATE_MEASUREMENT_UUID, self.heart_rate_handler)

            # 重置心跳时间
            self.last_heart_rate_time = time.time()

            # 启动心跳检测任务
            self.heartbeat_task = asyncio.create_task(self.heartbeat_monitor())

            self.root.after(0, self.show_monitor_view)
        except Exception as e:
            self.update_status(f"连接失败: {e}")
            self.root.after(0, self.reset_ui_on_disconnect)

    async def heartbeat_monitor(self):
        """监控心跳数据，如果超时则自动返回主界面"""
        while self.client and self.client.is_connected:
            await asyncio.sleep(1)

            # 检查是否超时
            if time.time() - self.last_heart_rate_time > self.connection_timeout:
                self.update_status("设备已停止心率广播，自动返回主界面")
                # 自动断开连接并返回主界面
                await self.disconnect_device()
                break

    def calculate_text_size(self, text, font_size):
        """计算文本的实际大小"""
        # 使用更大的放大倍数来提高抗锯齿质量
        scale = 4
        large_font_size = font_size * scale

        # 创建临时图像来计算文本大小
        temp_image = Image.new('RGBA', (1, 1), (0, 0, 0, 0))
        draw = ImageDraw.Draw(temp_image)

        # 获取字体路径
        font_path = self.get_font_path()

        # 尝试加载字体
        if font_path:
            try:
                font = ImageFont.truetype(font_path, large_font_size)
            except:
                font = ImageFont.load_default()
        else:
            font = ImageFont.load_default()

        # 计算文本边界框
        bbox = draw.textbbox((0, 0), text, font=font)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]

        # 缩小回实际大小
        text_width = text_width // scale
        text_height = text_height // scale

        # 添加一些边距
        padding = 20
        window_width = text_width + padding * 2
        window_height = text_height + padding * 2

        # 设置最小窗口大小
        min_width = 150
        min_height = 60

        window_width = max(window_width, min_width)
        window_height = max(window_height, min_height)

        return window_width, window_height

    def create_text_with_outline(self, width, height, text, font_size, font_color):
        """创建带阴影/描边的文本图像，背景完全透明"""
        # 使用更大的放大倍数来提高抗锯齿质量
        scale = 4
        large_width = width * scale
        large_height = height * scale
        large_font_size = font_size * scale

        # 创建完全透明的图像
        image = Image.new('RGBA', (large_width, large_height), (0, 0, 0, 0))
        draw = ImageDraw.Draw(image)

        # 获取字体路径
        font_path = self.get_font_path()

        # 尝试加载字体
        if font_path:
            try:
                font = ImageFont.truetype(font_path, large_font_size)
            except:
                font = ImageFont.load_default()
        else:
            font = ImageFont.load_default()

        # 计算文本位置（居中）
        bbox = draw.textbbox((0, 0), text, font=font)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
        x = (large_width - text_width) // 2
        y = (large_height - text_height) // 2

        # 转换颜色
        rgb_color = self.hex_to_rgb(font_color)

        # 绘制描边（使背景透明时文字更清晰）
        outline_width = scale * 2
        for offset_x in range(-outline_width, outline_width + 1):
            for offset_y in range(-outline_width, outline_width + 1):
                if offset_x != 0 or offset_y != 0:
                    draw.text(
                        (x + offset_x, y + offset_y),
                        text,
                        font=font,
                        fill=(255, 255, 255, 200)  # 白色半透明描边
                    )

        # 绘制主文本
        draw.text((x, y), text, font=font, fill=rgb_color + (255,))

        # 缩小图像实现抗锯齿
        image = image.resize((width, height), Image.LANCZOS)

        return image

    def hex_to_rgb(self, hex_color):
        """将十六进制颜色转换为 RGB"""
        hex_color = hex_color.lstrip('#')
        return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))

    def show_monitor_view(self):
        """显示心率监控窗口"""
        self.root.withdraw()
        self.monitor_window = tk.Toplevel(self.root)
        self.monitor_window.overrideredirect(True)
        self.monitor_window.attributes('-topmost', True)

        # 使用不常见的颜色作为透明色
        transparent_color = 'grey1'  # 几乎是黑色 #030303
        self.monitor_window.config(bg=transparent_color)
        self.monitor_window.attributes('-transparentcolor', transparent_color)

        font_color = self.config.get('font_color', '#000000')
        font_size = self.config.get('font_size', 32)
        # 实时获取最新的自定义文字
        custom_text = self.custom_text_var.get()

        # 计算初始文本大小并设置窗口大小
        initial_text = f"-- {custom_text}"
        window_width, window_height = self.calculate_text_size(initial_text, font_size)

        # 设置窗口位置
        if self.config.get("monitor_pos"):
            # 从保存的位置中提取坐标
            pos_str = self.config["monitor_pos"]
            if '+' in pos_str:
                parts = pos_str.split('+')
                if len(parts) >= 3:
                    x = parts[1]
                    y = parts[2]
                    self.monitor_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
                else:
                    self.monitor_window.geometry(f"{window_width}x{window_height}")
            else:
                self.monitor_window.geometry(f"{window_width}x{window_height}")
        else:
            # 居中显示
            screen_width = self.monitor_window.winfo_screenwidth()
            screen_height = self.monitor_window.winfo_screenheight()
            x = (screen_width - window_width) // 2
            y = (screen_height - window_height) // 2
            self.monitor_window.geometry(f"{window_width}x{window_height}+{x}+{y}")

        # 创建 Canvas
        self.canvas = tk.Canvas(
            self.monitor_window,
            bg=transparent_color,
            highlightthickness=0,
            width=window_width,
            height=window_height
        )
        self.canvas.pack()

        # 创建初始文本图像
        self.update_text_image(initial_text, window_width, window_height, font_size, font_color)

        # 创建右键菜单
        context_menu = tk.Menu(self.monitor_window, tearoff=0)
        context_menu.add_command(label="手动停止", command=self.start_disconnect)
        context_menu.add_command(label="重新连接", command=self.reconnect_device)
        context_menu.add_separator()
        context_menu.add_command(label="打开数据文件夹", command=self.open_data_folder)

        def show_context_menu(event):
            try:
                context_menu.tk_popup(event.x_root, event.y_root)
            finally:
                context_menu.grab_release()

        # 绑定事件
        self.canvas.bind("<Button-3>", show_context_menu)
        self.monitor_window.bind('<Button-1>', self.on_mouse_press)
        self.monitor_window.bind('<B1-Motion>', self.on_mouse_drag)

        # 绑定鼠标滚轮事件 - 支持不同平台
        self.canvas.bind("<MouseWheel>", self.on_mousewheel)  # Windows
        self.canvas.bind("<Button-4>", self.on_mousewheel)  # Linux 向上滚动
        self.canvas.bind("<Button-5>", self.on_mousewheel)  # Linux 向下滚动
        self.monitor_window.bind("<MouseWheel>", self.on_mousewheel)  # Windows
        self.monitor_window.bind("<Button-4>", self.on_mousewheel)  # Linux 向上滚动
        self.monitor_window.bind("<Button-5>", self.on_mousewheel)  # Linux 向下滚动

    def open_data_folder(self):
        """打开数据文件夹"""
        try:
            if platform.system() == "Windows":
                import subprocess
                subprocess.run(['explorer', str(DATA_DIR.absolute())])
            elif platform.system() == "Darwin":  # macOS
                import subprocess
                subprocess.run(['open', str(DATA_DIR.absolute())])
            else:  # Linux
                import subprocess
                subprocess.run(['xdg-open', str(DATA_DIR.absolute())])
            self.update_status(f"已打开数据文件夹: {DATA_DIR}")
        except Exception as e:
            self.update_status(f"打开文件夹失败: {e}")

    def on_mousewheel(self, event):
        """处理鼠标滚轮事件，调整字体大小"""
        try:
            # 获取当前字体大小，确保是整数
            current_font_size = int(float(self.font_size_var.get()))
        except (ValueError, TypeError):
            # 如果转换失败，使用默认值
            current_font_size = 32
            self.font_size_var.set(str(current_font_size))

        # 根据平台确定滚动方向
        if platform.system() == "Windows":
            # Windows: event.delta > 0 表示向上滚动
            delta = event.delta / 120
        else:
            # Linux: event.num == 4 表示向上滚动
            delta = 1 if event.num == 4 else -1

        # 调整字体大小，每次滚动调整2个单位
        # 确保结果是整数
        new_font_size = int(round(current_font_size + (2 * delta)))

        # 限制字体大小范围
        new_font_size = max(10, min(100, new_font_size))

        # 如果字体大小有变化，则更新
        if new_font_size != current_font_size:
            self.font_size_var.set(str(new_font_size))
            self.config['font_size'] = new_font_size
            self.save_config()

            # 更新显示
            self.update_display_with_new_font_size()
            self.update_status(f"字体大小已调整为: {new_font_size}")

    def update_display_with_new_font_size(self):
        """使用新的字体大小更新显示"""
        if not self.monitor_window or not hasattr(self, 'canvas'):
            return

        font_color = self.config.get('font_color', '#000000')
        font_size = int(self.font_size_var.get())
        custom_text = self.custom_text_var.get()

        # 获取当前显示的文本
        current_text = "-- " + custom_text

        # 尝试从状态栏获取当前心率
        status_text = self.status_label.cget("text")
        if status_text.startswith("心率: "):
            try:
                # 提取心率值
                hr_part = status_text.split(": ")[1].split(" ")[0]
                current_text = f"{hr_part} {custom_text}"
            except:
                pass

        # 计算新的窗口大小
        new_width, new_height = self.calculate_text_size(current_text, font_size)

        # 获取当前窗口位置
        current_geometry = self.monitor_window.geometry()
        if '+' in current_geometry:
            parts = current_geometry.split('+')
            if len(parts) >= 3:
                x = parts[1]
                y = parts[2]
                # 更新窗口大小，保持位置不变
                self.monitor_window.geometry(f"{new_width}x{new_height}+{x}+{y}")
            else:
                self.monitor_window.geometry(f"{new_width}x{new_height}")
        else:
            self.monitor_window.geometry(f"{new_width}x{new_height}")

        # 更新 Canvas 大小
        self.canvas.config(width=new_width, height=new_height)

        # 更新文本图像
        self.update_text_image(current_text, new_width, new_height, font_size, font_color)

    def update_text_image(self, text, width, height, font_size, font_color):
        """更新显示的文本"""
        # 创建新的文本图像
        image = self.create_text_with_outline(width, height, text, font_size, font_color)
        self.text_photo = ImageTk.PhotoImage(image)

        # 清空 canvas 并绘制新图像
        self.canvas.delete("all")
        self.canvas.create_image(0, 0, anchor='nw', image=self.text_photo)

    def on_mouse_press(self, event):
        self._offset_x = event.x
        self._offset_y = event.y

    def on_mouse_drag(self, event):
        x = self.monitor_window.winfo_pointerx() - self._offset_x
        y = self.monitor_window.winfo_pointery() - self._offset_y
        self.monitor_window.geometry(f'+{x}+{y}')

    def heart_rate_handler(self, sender, data):
        """心率数据处理器"""
        try:
            # 更新最后接收时间
            self.last_heart_rate_time = time.time()

            # 解析心率数据
            heart_rate = data[1]

            # 保存心率数据到CSV文件
            self.save_heart_rate_data(heart_rate)

            if self.monitor_window and hasattr(self, 'canvas'):
                font_color = self.config.get('font_color', '#000000')
                font_size = self.config.get('font_size', 32)
                # 实时获取最新的自定义文字
                custom_text = self.custom_text_var.get()

                # 构建显示文本
                display_text = f"{heart_rate} {custom_text}"

                # 计算新的窗口大小
                new_width, new_height = self.calculate_text_size(display_text, font_size)

                # 获取当前窗口位置
                current_geometry = self.monitor_window.geometry()
                if '+' in current_geometry:
                    parts = current_geometry.split('+')
                    if len(parts) >= 3:
                        x = parts[1]
                        y = parts[2]
                        # 更新窗口大小，保持位置不变
                        self.monitor_window.geometry(f"{new_width}x{new_height}+{x}+{y}")
                    else:
                        self.monitor_window.geometry(f"{new_width}x{new_height}")
                else:
                    self.monitor_window.geometry(f"{new_width}x{new_height}")

                # 更新 Canvas 大小
                self.canvas.config(width=new_width, height=new_height)

                # 更新文本图像
                self.update_text_image(display_text, new_width, new_height, font_size, font_color)

                # 更新状态栏
                self.update_status(f"心率: {heart_rate} {custom_text} (已保存)")
        except Exception as e:
            self.update_status(f"心率数据解析错误: {e}")

    def reconnect_device(self):
        """重新连接设备 - 先断开再连接"""
        if self.monitor_window:
            self.monitor_window.destroy()
            self.monitor_window = None

        self.root.deiconify()
        self.update_status("正在断开当前连接...")

        # 先执行断开连接操作
        if self.client and self.client.is_connected:
            asyncio.create_task(self.disconnect_and_reconnect())
        else:
            # 如果没有连接，直接开始连接
            self.update_status("准备重新连接...")
            self.start_connect()

    async def disconnect_and_reconnect(self):
        """先断开连接，然后再重新连接"""
        try:
            # 停止心跳监控
            if self.heartbeat_task:
                self.heartbeat_task.cancel()
                self.heartbeat_task = None

            if self.client:
                if self.client.is_connected:
                    await self.client.stop_notify(HEART_RATE_MEASUREMENT_UUID)
                    await self.client.disconnect()

                self.client = None

            # 断开连接后，等待一小段时间确保完全断开
            await asyncio.sleep(1)

            # 更新状态并开始重新连接
            self.root.after(0, lambda: self.update_status("准备重新连接..."))
            self.root.after(0, self.start_connect)
        except Exception as e:
            self.root.after(0, lambda: self.update_status(f"断开连接时出错: {e}"))
            self.root.after(0, self.start_connect)  # 即使出错也尝试重新连接

    def start_disconnect(self):
        """手动断开连接"""
        if self.client and self.client.is_connected:
            self.update_status("手动断开连接...")
            asyncio.create_task(self.disconnect_device())

    async def disconnect_device(self):
        """断开设备连接"""
        # 停止心跳监控
        if self.heartbeat_task:
            self.heartbeat_task.cancel()
            self.heartbeat_task = None

        if self.client:
            try:
                if self.client.is_connected:
                    await self.client.stop_notify(HEART_RATE_MEASUREMENT_UUID)
                    await self.client.disconnect()
            except Exception as e:
                self.update_status(f"断开时出错: {e}")
            finally:
                self.client = None
                # 自动返回主界面
                self.root.after(0, self.reset_ui_on_disconnect)

    def reset_ui_on_disconnect(self):
        """重置UI并返回主界面"""
        self.save_config()
        if self.monitor_window:
            self.monitor_window.destroy()
            self.monitor_window = None

        self.root.deiconify()
        self.connect_button.config(state=tk.NORMAL if self.device_listbox.curselection() else tk.DISABLED)
        self.start_continuous_scan()

    def update_status(self, text):
        """更新状态栏"""
        self.status_label.config(text=text)

    def on_closing(self):
        """程序关闭时的清理工作"""
        self.save_config()
        self.is_scanning = False

        # 取消所有异步任务
        if self.scan_task:
            self.scan_task.cancel()
        if self.heartbeat_task:
            self.heartbeat_task.cancel()
        if self.connect_task:  # 添加连接任务取消
            self.connect_task.cancel()

        # 直接断开连接（不创建新任务）
        if self.client and self.client.is_connected:
            # 使用同步方式断开连接
            try:
                loop = asyncio.get_event_loop()
                if loop.is_running():
                    # 如果事件循环正在运行，创建任务并立即等待
                    asyncio.create_task(self.disconnect_device())
                else:
                    # 如果事件循环已停止，直接运行
                    loop.run_until_complete(self.disconnect_device())
            except Exception as e:
                print(f"断开连接时出错: {e}")

        self.root.destroy()


async def main():
    root = tk.Tk()
    app = HeartRateApp(root)
    root.protocol("WM_DELETE_WINDOW", app.on_closing)

    try:
        while True:
            root.update()
            root.update_idletasks()
            await asyncio.sleep(0.05)
    except tk.TclError:
        pass
    finally:
        # 确保所有任务被取消
        tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
        for task in tasks:
            task.cancel()
        # 等待所有任务完成
        await asyncio.gather(*tasks, return_exceptions=True)

        # 确保客户端断开
        if app.client and app.client.is_connected:
            await app.disconnect_device()


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except (KeyboardInterrupt, asyncio.CancelledError):
        pass
